#include "db_module.h"

#include <boost/asio.hpp>
using namespace boost;
using namespace boost::asio;

#include <google/protobuf/descriptor.h>
#include <google/protobuf/message.h>
using namespace google::protobuf;

#include <common/base/server_mgr.h>
#include <common/log/log.h>
#include <common/net/message_parse.h>
#include <common/net/session.h>
#include <common/utils/util.h>

#include <protocols/protos/common.pb.h>
using namespace proto::common;
#include <protocols/protos/login.pb.h>
using namespace proto::login;
#include <protocols/protos/db.pb.h>
using namespace proto::db;


#include "base/db_mgr.h"
#include "base/player.h"
#include "base/sub_server_mgr.h"



bool DbModule::doDispatch(const std::shared_ptr<SubServerMgr>& submgr,
                          const std::shared_ptr<Player>& player,
                          const MessageID& msgID,
                          const char* buf,
                          std::size_t bufSize)
{
    switch (msgID.stMsg.msgID)
    {
    case S2DB_ADD_REQ:
        return onAdd(submgr, player, msgID, buf, bufSize);

    case S2DB_DEL_REQ:
        return onDel(submgr, player, msgID, buf, bufSize);

    case S2DB_UPDATE_REQ:
        return onUpdate(submgr, player, msgID, buf, bufSize);

    case S2DB_QUERY_REQ:
        return onQuery(submgr, player, msgID, buf, bufSize);

    default:
        return false;
    }

    return false;
}

bool processTables(const DbReq& req,
                   const std::function<bool (const proto::db::TableInfo& tableInfo,
                                             const google::protobuf::Message* msg,
                                             const google::protobuf::Descriptor* des,
                                             std::vector<std::string>& sqls)>& createSqlFunc)
{
    auto dbMgr = ServerMgr::get().getSubServ<SubServerMgr>()->getSubMgr<DBMgr>(SubManagerType::eDB);
    if (!dbMgr)
    {
        LOG_ERROR("No db manager was found.");
        return false;
    }

    std::vector<std::string> sqls;

    for (int i = 0; i < req.tables_size(); ++i)
    {
        auto tableInfo = req.tables(i);
        LOG_DEBUG(" table full name:" << tableInfo.table_proto_name());

        auto des = google::protobuf::DescriptorPool::generated_pool()->FindMessageTypeByName(tableInfo.table_proto_name());
        if (!des)
        {
            LOG_ERROR("Add table No found table descriptor: " << tableInfo.table_proto_name());
            return false;
        }
        auto prototype = google::protobuf::MessageFactory::generated_factory()->GetPrototype(des);
        if (!prototype)
        {
            LOG_ERROR("Add table No found table prototype define: " << tableInfo.table_proto_name());
            return false;
        }
        const auto& msg = prototype->New();
        if (!msg)
        {
            LOG_ERROR("Create proto table failed.");
            return false;
        }
        if (!msg->ParseFromString(tableInfo.table_buffer()))
        {
            LOG_ERROR("Parse msg failed.");
            return false;
        }

        if (!createSqlFunc(tableInfo, msg, des, sqls))
        {
            LOG_ERROR("Create sql failed.");
            return false;
        }
    }
    if (!dbMgr->executeSql(sqls))
    {
        LOG_ERROR("execute sql failed.");
        return false;
    }
    return true;
}

bool DbModule::onAdd(const std::shared_ptr<SubServerMgr>& submgr,
                     const std::shared_ptr<Player>& player,
                     const MessageID& msgID,
                     const char* buf,
                     std::size_t bufSize)
{
    DbReq req;
    if (!req.ParseFromArray(buf, bufSize))
    {
        LOG_ERROR("Parse proto data failed.");
        return false;
    }

    auto func = [](const proto::db::TableInfo& tableInfo,
                     const google::protobuf::Message* msg,
                     const google::protobuf::Descriptor* des,
                     std::vector<std::string>& sqls) -> bool
   {
        auto ref = msg->GetReflection();

        // insert into tablename(fieldname1, fieldname2) values (value1, value2);
        std::stringstream ss;
        ss << "insert into " << des->name() << "(";
        std::stringstream ssf, ssv;
        if (des->field_count() == 0)
        {
            return true;
        }

        bool first = true;
        for (int i = 0; i < des->field_count(); ++i)
        {
            auto field = des->field(i);
            if (!isSupportFieldType(ref, msg, field))
            {
                LOG_ERROR("field type not support, table:" << tableInfo.table_proto_name()
                          << " field:" << field->name());
                return false;
            }

            if (!ref->HasField(*msg, field))
            {
                if (field->is_required())
                {
                    LOG_ERROR("required field not set, table:" << tableInfo.table_proto_name()
                              << " field:" << field->name());
                    return false;
                }
                else
                {
                    continue;
                }
            }
            else
            {
                if (!first)
                {
                    ssf << " , ";
                    ssv << " , ";
                }
                ssf <<  field->name();
                ssv << getFieldAsString(ref, msg, field);
                first = false;
            }
        }
        ss << ssf.str() << " ) values ( " << ssv.str() << ")";
        sqls.push_back(ss.str());
        LOG_DEBUG("execute sql:" << ss.str());
        return true;
    };
    DbRet ret;
    if (!processTables(req, func))
    {
        ret.set_err(proto::db::eFailed);
        LOG_ERROR("execute update failed.");
        return player->sendMsg(MessageID(req.ret_module_type(), req.ret_msg_id()), ret);
    }

    ret.set_err(proto::db::eSuc);
    return player->sendMsg(MessageID(req.ret_module_type(), req.ret_msg_id()), ret);
}

bool DbModule::onDel(const std::shared_ptr<SubServerMgr>& submgr,
                     const std::shared_ptr<Player>& player,
                     const MessageID& msgID,
                     const char* buf,
                     std::size_t bufSize)
{
    DbReq req;
    if (!req.ParseFromArray(buf, bufSize))
    {
        LOG_ERROR("Parse proto data failed.");
        return false;
    }

    auto func = [](const proto::db::TableInfo& tableInfo,
                   const google::protobuf::Message *msg,
                   const google::protobuf::Descriptor *des,
                   std::vector<std::string>& sqls) -> bool
    {
        auto ref = msg->GetReflection();

        // update tablename set fieldname1=value1, fieldname2=value2 where id = 1
        std::stringstream ss;
        ss << "delete from " << des->name() << " where ";
        std::stringstream ssw;
        if (des->field_count() == 0)
        {
            return true;
        }

        bool firstwhere = true;
        for (int i = 0; i < des->field_count(); ++i)
        {
            auto field = des->field(i);
            if (!isSupportFieldType(ref, msg, field))
            {
                LOG_ERROR("field type not support, table:" << tableInfo.table_proto_name()
                          << " field:" << field->name());
                return false;
            }

            if (!ref->HasField(*msg, field))
            {
                if (field->is_required())
                {
                    LOG_ERROR("required field not set, table:" << tableInfo.table_proto_name()
                              << " field:" << field->name());
                    return false;
                }
                else
                {
                    continue;
                }
            }
            else
            {
                if (!field->is_required())
                {
                    continue;
                }
                if (!firstwhere)
                {
                    ssw << " and ";
                }
                ssw << field->name() << " = " << getFieldAsString(ref, msg, field) << " ";
                firstwhere = false;
            }
        }
        ss << ssw.str();
        sqls.push_back(ss.str());
        LOG_DEBUG("execute sql:" << ss.str());
        return true;
    };
    DbRet ret;
    if (!processTables(req, func))
    {
        ret.set_err(proto::db::eFailed);
        LOG_ERROR("execute update failed.");
        return player->sendMsg(MessageID(req.ret_module_type(), req.ret_msg_id()), ret);
    }

    ret.set_err(proto::db::eSuc);
    return player->sendMsg(MessageID(req.ret_module_type(), req.ret_msg_id()), ret);
}

bool DbModule::onUpdate(const std::shared_ptr<SubServerMgr>& submgr,
                        const std::shared_ptr<Player>& player,
                        const MessageID& msgID,
                        const char* buf,
                        std::size_t bufSize)
{
    DbReq req;
    if (!req.ParseFromArray(buf, bufSize))
    {
        LOG_ERROR("Parse proto data failed.");
        return false;
    }

    auto func = [](const proto::db::TableInfo& tableInfo,
                     const google::protobuf::Message *msg,
                     const google::protobuf::Descriptor *des,
                     std::vector<std::string>& sqls) -> bool
    {
        auto ref = msg->GetReflection();

        // update tablename set fieldname1=value1, fieldname2=value2 where id = 1
        std::stringstream ss;
        ss << "update " << des->name() << " set ";
        std::stringstream sss, ssw;
        ssw << " where ";
        if (des->field_count() == 0)
        {
            return true;
        }

        bool firstset = true, firstwhere = true;
        for (int i = 0; i < des->field_count(); ++i)
        {
            auto field = des->field(i);
            if (!isSupportFieldType(ref, msg, field))
            {
                LOG_ERROR("field type not support, table:" << tableInfo.table_proto_name()
                          << " field:" << field->name());
                return false;
            }

            if (!ref->HasField(*msg, field))
            {
                if (field->is_required())
                {
                    LOG_ERROR("required field not set, table:" << tableInfo.table_proto_name()
                              << " field:" << field->name());
                    return false;
                }
                else
                {
                    continue;
                }
            }
            else
            {
                if (field->is_required())
                {
                    if (!firstwhere)
                    {
                        ssw << " , ";
                    }
                    ssw << field->name() << " = " << getFieldAsString(ref, msg, field) << " ";
                    firstwhere = false;
                }
                else
                {
                    if (!firstset)
                    {
                        sss << " , ";
                    }
                    sss << field->name() << " = " << getFieldAsString(ref, msg, field) << " ";
                    firstset = false;
                }
            }
        }
        ss << sss.str() << ssw.str();
        sqls.push_back(ss.str());
        LOG_DEBUG("execute sql:" << ss.str());
        return true;
    };

    DbRet ret;
    if (!processTables(req, func))
    {
        ret.set_err(proto::db::eFailed);
        LOG_ERROR("execute update failed.");
        return player->sendMsg(MessageID(req.ret_module_type(), req.ret_msg_id()), ret);
    }

    ret.set_err(proto::db::eSuc);
    return player->sendMsg(MessageID(req.ret_module_type(), req.ret_msg_id()), ret);
    return true;
}

bool DbModule::onQuery(const std::shared_ptr<SubServerMgr>& submgr,
                       const std::shared_ptr<Player>& player,
                       const MessageID& msgID,
                       const char* buf,
                       std::size_t bufSize)
{
    LOG_WARN(" QUERY NOT SUPPORT.");
    return true;
}
